#!/usr/bin/python
#torrent stuff
from BitTorrent.download import download,defaults
from BitTorrent.fmt import fmttime, fmtsize
from BitTorrent.bencode import bdecode,bencode
from threading import Thread, Event, Lock
from status import *
#utils
from os import listdir
from os.path import abspath, join, exists, getsize
from sys import argv, stdout, exit
from time import sleep
from copy import deepcopy
import traceback
import sha

#server stuff
import time
import BaseHTTPServer
import cgi
from json.json import JsonWriter,JsonReader

#global thread pool
threads={}
torrents={}
##        torrents[id]={'id':id,'path':self.get_form['torrent'][0],
##            'save_path':self.get_form['save_path'][0]})
torrent_metas=[]
main=None
#messages sorted by id
#each id points to a list of strings,which are json objects
torrent_messages={}
filecheck = Lock()
started=False
#keepgoing is an ugly hack used to shut this beast down
keepgoing=True
###HTTPD server instance
httpd=None
server=None
##threads['id']={}
##StatusUpdater(None,None,'id',output=torrent_messages,threads=threads)
##initialize Json Stuff
json_writer=JsonWriter()
json_reader=JsonReader()
def mainloop(params=[]):
    global torrents,threads,torrent_metas, torrent_messages
    print 'mainloop starting'
##    print torrents
    deadtorrents=[]
##    initialize_messages()
##    print torrent_messages
    while True:
        #initialize torrents
        for key in torrents.keys():
            if key not in threads.keys():
                torrent_messages[key]['error'].append("New Torrent: %s" %key)
                threads[key]={'kill':Event(),'try': 1}
                threads[key]['thread']=Thread(
                    target=StatusUpdater(key,params,output=torrent_messages[key],threads=threads,
                        save_path=torrents[key]['save_path'],torrents=torrents).download,
                        name=key
                    )
                threads[key]['thread'].start()
##        print "threads=",threads
##        print
        for name,threadinfo in threads.items():
            if threadinfo.get('timeout')==0:
                threadinfo['try'] = threadinfo['try'] + 1
                print name
                print threads
                if not threadinfo['kill'].isSet():
                    threadinfo['thread']=Thread(
                        StatusUpdater(name,params,output=torrent_messages[name],threads=threads,
                        save_path=torrents[name]['save_path'],torrents=torrents).download,
                        name=name
                    )
                    threadinfo['thread'].start()
                    threadinfo['timeout'] = -1
                else:
                    del threads[name]
                    del torrents[name]

            elif threadinfo.get('timeout') > 0:
                #decrement our counter by 1
                threadinfo['timeout']=threadinfo['timeout'] - 1
            elif not threadinfo['thread'].isAlive():
                if threadinfo.get('checking',None):
                    filecheck.release()
                if threadinfo.get('try') == 6:
                    deadtorrents.append(name)
                    torrent_messages[name]['error'].append("%s died 6 times, added to dead list" %name)
                    del threads[name]
                else:
                    del threadinfo['thread']
                    threadinfo['timeout']=10
            #TODO: maybe deal the torrents that disapear.
            #or get canceled, for later in the devel cycle
##        print torrent_messages
        print threads
##        return
        sleep(1)

def dropdir_mainloop(d, params):
    """
        d=directory to look for torrents in
        params=a string passed from the command line
    """
    deadfiles = []
    global threads, status,keepgoing
    while keepgoing:
        files = listdir(d)
        # new files
        for file in files: 
            if file[-len(ext):] == ext:
                if file not in threads.keys() + deadfiles:
                    threads[file] = {'kill': Event(), 'try': 1}
                    print 'New torrent: %s' % file
                    stdout.flush()
                    threads[file]['thread'] = Thread(target = StatusUpdater(join(d, file), params, file).download, name = file)
                    threads[file]['thread'].start()
        # files with multiple tries
        for file, threadinfo in threads.items():
            if threadinfo.get('timeout') == 0:
                # Zero seconds left, try and start the thing again.
                threadinfo['try'] = threadinfo['try'] + 1
                threadinfo['thread'] = Thread(target = StatusUpdater(join(d, file), params, file).download, name = file)
                threadinfo['thread'].start()
                threadinfo['timeout'] = -1
            elif threadinfo.get('timeout') > 0: 
                # Decrement our counter by 1
                threadinfo['timeout'] = threadinfo['timeout'] - 1
            elif not threadinfo['thread'].isAlive():
                # died without permission
                # if it was checking the file, it isn't anymore.
                if threadinfo.get('checking', None):
                    filecheck.release()
                if threadinfo.get('try') == 6: 
                    # Died on the sixth try? You're dead.
                    deadfiles.append(file)
                    print '%s died 6 times, added to dead list' % fil
                    stdout.flush()
                    del threads[file]
                else:
                    del threadinfo['thread']
                    threadinfo['timeout'] = 10
            # dealing with files that dissapear
            if file not in files:
                print 'Torrent file dissapeared, killing %s' % file
                stdout.flush()
                if threadinfo.get('timeout', -1) == -1:
                    threadinfo['kill'].set()
                    threadinfo['thread'].join()
                # if this thread was filechecking, open it up
                if threadinfo.get('checking', None): 
                    filecheck.release()
                del threads[file]
        for file in deadfiles:
            # if the file dissapears, remove it from our dead list
            if file not in files: 
                deadfiles.remove(file)
        sleep(1)

class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
    """
        Request object that handles http requests, also
        does some torrent bookeeping
        all responses are in JSON serialized format as
        all messages are to be consumed by javascript
    """

    def torrent_file(self,msg):
        global torrents,threads,torrent_metas
        #decode data file and put it in the torrent metas
        print "torrent_file decoded get=", self.get_form,"path=",self.path
        data=open(self.get_form['torrent'][0],'rb').read()
        id=sha.new()
        id.update(self.get_form['torrent'][0])
        id=id.hexdigest()
        ##TODO: remove the pieces info and make sure the path is in there
        torrents[id]={'id':id,'path':self.get_form['torrent'][0],
            'save_path':self.get_form['save_path'][0]}
        torrent_messages[id]=dict(error=[],status=str(),)
        self.wfile.write(json_writer.write(dict(id=id)))
    def get_dispatch(self):
        methods=dict(
            form=self.form,
            torrent_file=self.torrent_file,
            start=self.start,
            info_all=self.info_all,
            info=self.info,
            kill=self.kill,
            stop=self.stop,
        )
        self.get_form=cgi.parse_qs(self.path.split('/')[-1][1::])
        method=self.path.split('/')[1]
        return methods.get(method,self.bad_method)(method)
    def bad_method(self,msg):
        return self.wfile.write(json_writer.write(dict(error="no method found, %s" %msg)))

    def start(self,msg):
        print "starting torrents"
        global torrents,threads,torrent_metas
        global main,started
        if started:
            print "Service already started"
            return self.wfile.write(json_writer.write(dict(message='torrents started already')))
        #crop the pieces info because it's not needed
        #will bloat memory
##        for n,v in enumerate(torrent_metas):
##            del torrent_metas[n]['info']['pieces']
        params=[]
        started=True
        main=Thread(target=mainloop,name="mainloop",kwargs=dict(params=params))
        main.start()
        if started:
            return self.wfile.write(json_writer.write(dict(message='All Torrents Started')))
    def kill(self,msg):
        id=self.get_form['id'][0]
        threads[id]['kill'].set()
        threads[id]['thread'].join()
        self.wfile.write(json_writer.write(dict(message='%s killed' %id)))
    def stop(self,msg):
        global keepgoing,server,httpd
        print "*"*80
        print 'STOPING'*3
        print "*"*80
        for k in threads.keys():
            threads[k]['kill'].set() 
            threads[k]['thread'].join() 
        httpd.server_close()
        keepgoing=False;
    def info(self,msg):
        id=self.get_form['id'][0]
        self.wfile.write(json_writer.write(dict(status=threads[id]['status'],
                savefile=threads[id]['savefile'],
                downtotal=threads[id].get('downtotal',0),
                downfile=threads[id]['downfile'],
                errors=threads[id]['errors'],
                downrate=threads[id].get('downrate',0),
                uprate=threads[id].get('uprate',0),
                filesize=threads[id].get('filesize',0),
                uptotal=threads[id].get('uptotal',0)
            )))

    def info_all(self,msg): 
##        print "info_all threads=",threads
        ret={}
        t=threads
##        print t
        if len(t)>0:
            print "creating returnable info dict"
            for k in threads.keys():
                row=dict(status=t[k]['status'],
                        savefile=t[k].get('savefile',''),
                        downtotal=t[k].get('downtotal',0),
                        downfile=t[k].get('downfile',''),
                        errors=t[k].get('errors',''),
                        downrate=t[k].get('downrate',0),
                        uprate=t[k].get('uprate',0),
                        filesize=t[k].get('filesize',0),
                        uptotal=t[k].get('uptotal',0)
                    )
                ret[k]=row

##        print 'ret=',ret
        self.wfile.write(json_writer.write(ret))

    def do_GET(self):
        """Respond to a GET request."""
        return self.get_dispatch()
    def form(self,msg):
        self.html_header()
        self.wfile.write(open('index.html','r').read())
    def plain_header(self):
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.end_headers()
    def html_header(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()

if __name__ == '__main__':
    print "argv=",argv
    global httpd,keepgoing,server
    port=int(argv[1])
    server_class = BaseHTTPServer.HTTPServer
    httpd = server_class(('localhost', port), Handler)
    print time.asctime(), "Server Starts - %s:%s" % ('localhost', port)
    try:
        server=Thread(target=httpd.serve_forever,name='http_server')
        server.start()
        #to keep this from running off the end
        while keepgoing: sleep(1)
    except KeyboardInterrupt:
        pass
    httpd.server_close()
    print time.asctime(), "Server Stops - %s:%s" % ('localhost',port)
    exit(0)
